package cn.backflow.secure.interceptor;

import cn.backflow.secure.Subject;
import cn.backflow.secure.annotation.Authorization;
import cn.backflow.secure.exception.PermissionDeniedException;
import cn.backflow.secure.exception.UnauthorizedException;
import org.aopalliance.intercept.MethodInvocation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Set;

/**
 * 拦截@Permission注解的方法进行权限验证
 * <p>
 * Created by hunan on 2017/5/21.
 */
@Component
public class PermissionInterceptor extends HandlerInterceptorAdapter {

    public static String unAuthenticatedMessage = "未登录或登录已超时，请重新登录。";
    public static String unAuthorizedMessage = "您没有权限执行此操作，请检查账号的权限级别。";
    public static String illegalArgument = "请求参数错误。";
    public static String illegalRequest = "非法请求。";
    public static String systemBusy = "系统繁忙，请稍后再试。";
    public static String systemError = "系统内部错误，请联系管理员。";

    private static Logger LOG = LoggerFactory.getLogger(PermissionInterceptor.class);

    // @Resource
    // private UserService userService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        String request_info = String.format("%s %s FROM: %s", request.getMethod(), request.getRequestURI(), request.getHeader("HOST"));
        LOG.info(request_info);

        if (handler instanceof HandlerMethod) {
            Authorization permission = getAnnotation((HandlerMethod) handler, Authorization.class);
            if (permission == null) { // 没有权限标注, 直接放行
                return true;
            }
            boolean authorized = checkPermission(permission.value(), request);
            if (!authorized) {
                LOG.info("Unauthorized request: {}, required permission: {}", request_info, permission.value());
                throw new PermissionDeniedException(unAuthorizedMessage);
            }
        }
        return true;
    }

    public Annotation getAnnotation(MethodInvocation mi, Class<? extends Annotation> clazz) {
        Method m = mi.getMethod();

        Annotation a = AnnotationUtils.findAnnotation(m, clazz);
        if (a != null) return a;

        //The MethodInvocation's method object could be a method defined in an interface.
        //However, if the annotation existed in the interface's implementation (and not
        //the interface itself), it won't be on the above method object.  Instead, we need to
        //acquire the method representation from the targetClass and check directly on the
        //implementation itself:
        Class<?> targetClass = mi.getThis().getClass();
        m = ClassUtils.getMostSpecificMethod(m, targetClass);
        a = AnnotationUtils.findAnnotation(m, clazz);
        if (a != null) return a;
        // See if the class has the same annotation
        return AnnotationUtils.findAnnotation(mi.getThis().getClass(), clazz);
    }

    /**
     * 权限验证
     *
     * @param permission 权限标识
     * @param request    {@link HttpServletRequest}
     * @return authorized
     */
    @SuppressWarnings("unchecked")
    private boolean checkPermission(final String permission, HttpServletRequest request) throws Exception {

        // 判断当前用户是否登录
        Subject user = new Subject();
        if (user == null) {
            throw new UnauthorizedException(unAuthenticatedMessage);
        }

        Set<String> permissions = getPermissions(request);

        return permissions != null && permissions.contains(permission);
    }

    private Set<String> getPermissions(HttpServletRequest request) {
        return null;
    }

    private <T extends Annotation> T getAnnotation(HandlerMethod handlerMethod, Class<T> clazz) {
        T annotation = handlerMethod.getMethodAnnotation(clazz);
        if (annotation != null) {
            return annotation;
        }
        return AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), clazz);
    }
}
